package com.zmn.uac.api.controllers.callback;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.dingtalk.oapi.lib.aes.DingTalkEncryptor;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.uac.common.constant.DingTalkConstant;
import com.zmn.uac.common.constant.RedisKeyConstant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Arrays;

/**
 * 类描述：钉钉回调
 *
 * @author xujie
 * @since 2021/05/24 10:25
 */
@Api(tags = {"钉钉回调管理"})
@Slf4j
@RestController
@RequestMapping("/ding")
public class DingTalkController {
    private static final String TAG = "钉钉回调";

    @NacosValue(value = "${ding.talk.suite.key}", autoRefreshed = true)
    String suiteKey;

    @NacosValue(value = "${ding.talk.token}", autoRefreshed = true)
    String token;

    @NacosValue(value = "${ding.talk.aes.key}", autoRefreshed = true)
    String aesKey;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 钉钉回调
     * @param signature 签名
     * @param timestamp 时间戳
     * @param nonce 随机数
     * @param body 请求体
     * @return Object
     */
    @ApiOperation(value = "钉钉回调")
    @PostMapping(value = "/dingCallback")
    public Object dingCallback(@RequestParam(value = "signature") String signature, @RequestParam(value = "timestamp") Long timestamp,
                               @RequestParam(value = "nonce") String nonce, @RequestBody(required = false) JSONObject body) {

        String params = "signature:" + signature + " timestamp:" + timestamp + " nonce:" + nonce + " body:" + body;

        log.info("[{}], begin callback2: {} ", TAG, params);
        log.info("[{}], 参数：token: {}, aes: {} , key: {}", TAG, token, aesKey, suiteKey);
        log.info("AES反编译：" + Arrays.toString(Base64.decodeBase64(aesKey + "=")));

        try {
            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor(token, aesKey, suiteKey);
            // 从post请求的body中获取回调信息的加密数据进行解密处理
            String encrypt = body.getString("encrypt");
            String plainText = dingTalkEncryptor.getDecryptMsg(signature, timestamp.toString(), nonce, encrypt);
            JSONObject callBackContent = JSON.parseObject(plainText);

            // 根据回调事件类型做不同的业务处理
            String eventType = callBackContent.getString("EventType");
            switch (eventType){
                case DingTalkConstant.EVENT_CHECK_CREATE_SUITE_URL:
                    log.info("[{}], 验证新创建的回调URL有效性: {}", TAG, plainText);
                    break;
                case DingTalkConstant.EVENT_CHECK_UPADTE_SUITE_URL:
                    log.info("[{}], 验证更新回调URL有效性: {}", TAG, plainText);
                    break;
                case DingTalkConstant.EVENT_SUITE_TICKET:
                    // suite_ticket用于用签名形式生成accessToken(访问钉钉服务端的凭证)，需要保存到应用的db。钉钉会定期向本callback url推送suite_ticket新值用以提升安全性。应用在获取到新的时值时，保存db成功后，返回给钉钉success加密串（如本demo的return）
                    log.info("[{}], 应用suite_ticket数据推送: {}", TAG, plainText);
                    String suiteTicket = callBackContent.getString("SuiteTicket");
                    redisTemplate.opsForValue().set(RedisKeyConstant.UAC_DING_TALK_CALLBACK_SUITE_TICKET, suiteTicket);

                    break;
                case DingTalkConstant.EVENT_TMP_AUTH_CODE:
                    // 本事件应用应该异步进行授权开通企业的初始化，目的是尽最大努力快速返回给钉钉服务端。用以提升企业管理员开通应用体验,即使本接口没有收到数据或者收到事件后处理初始化失败都可以后续再用户试用应用时从前端获取到corpId并拉取授权企业信息，进而初始化开通及企业。
                    log.info("[{}], 企业授权开通应用事件: " + plainText);
                    break;
                default:
                    log.info("钉钉事件回调");
                    break;
            }

            // 返回success的加密信息表示回调处理成功
            return dingTalkEncryptor.getEncryptedMap("success", timestamp, nonce);
        } catch (Exception e) {
            //失败的情况，应用的开发者应该通过告警感知，并干预修复
            log.error("[{}], process callback fail {}, msg: {}", TAG, params, e.getMessage(), e);
            return "fail";
        }
    }


    /**
     * 获取SuiteTicket
     * @return ResponseDTO<String>
     */
    @ApiOperation(value = "获取SuiteTicket")
    @GetMapping(value = "/getSuiteTicket")
    public ResponseDTO<String> getSuiteTicket() {
        return ResponseDTO.success(redisTemplate.opsForValue().get(RedisKeyConstant.UAC_DING_TALK_CALLBACK_SUITE_TICKET));
    }
}
